/*
 *  Copyright 1999-2004 The Apache Sofware Foundation.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.tomcat.startup;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Hashtable;
import java.util.Properties;

import org.apache.tomcat.util.IntrospectionUtils;
import org.apache.tomcat.util.res.StringManager;
// Depends: StringManager, resources


/**
 * This task will stop tomcat
 *
 * @author Costin Manolache
 */
public class StopTomcat {
    static final String DEFAULT_CONFIG = "conf/server.xml";
    private static StringManager sm =
            StringManager.getManager("org.apache.tomcat.resources");

    String tomcatHome = null;

    String host = null;
    int port = -1;
    String secret;
    // explicit command line params ( for port, host or secret )
    boolean commandLineParams = false;
    String secretFile = null;
    String shutdown = null;
    String args[];
    boolean help = false;
    boolean isAjp13 = false;
    boolean isAjp12 = false;

    public StopTomcat() {
    }

    // -------------------- Parameters --------------------
    public void setSecretFile(String s) {
        secretFile = s;
        commandLineParams = true;
    }

    public void setAjpid(String s) {
        secretFile = s;
        commandLineParams = true;
    }

    public void setAjpid12(String s) {
        secretFile = s;
        commandLineParams = true;
        isAjp12 = true;
        isAjp13 = false;
    }

    public void setAjpid13(String s) {
        secretFile = s;
        commandLineParams = true;
        isAjp12 = false;
        isAjp13 = true;
    }

    public void setH(String s) {
        tomcatHome = s;
        System.getProperties().put("tomcat.home", s);
    }

    public void setHome(String s) {
        tomcatHome = s;
        System.getProperties().put("tomcat.home", s);
    }

    public void setHost(String h) {
        host = h;
        commandLineParams = true;
    }

    public void setPort(int port) {
        this.port = port;
        commandLineParams = true;
    }

    /**
     * When tomcat is started, a secret ( random ) key will be generated
     * in ajp12.id. If you run StopTomcat from the same host, it'll
     * read the key and use it. If you run from a different host, you'll
     * have to specify it manually
     */
    public void setPass(String s) {
        secret = s;
        commandLineParams = true;
    }

    public void setSecret(String s) {
        secret = s;
        commandLineParams = true;
    }

    public void setHelp(boolean b) {
        help = b;
    }

    // Generic properties / attributes

    public void setAttribute(String s, Object o) {
        if (s.equals("args"))
            setArgs((String[]) o);
    }

    public void setProperty(String name, String v) {
        if (!name.equals("stop"))
            // unknown property
            help = true;
    }

    public void setAjp13(boolean b) {
        isAjp13 = b;
        isAjp12 = !b;
    }

    public void setAjp12(boolean b) {
        isAjp12 = b;
        isAjp13 = !b;
    }

    public void setArgs(String args[]) {
        this.args = args;
    }

    // -------------------- Ant execute --------------------

    public void execute() throws Exception {
        if (args != null)
            processArgs(args);
        if (help) {
            printUsage();
            return;
        }
        System.out.println(sm.getString("tomcat.stop"));
        try {
            stopTomcat(); // stop serving
        } catch (java.net.ConnectException ex) {
            System.out.println(sm.getString("tomcat.connectexception"));
        } catch (Exception te) {
            throw te;
        }
        return;
    }

    // -------------------- Implementation --------------------

    void stopTomcat() throws Exception {
        // if a parameter isn't set, try to read it from a file
        if (port < 0 || host == null || secret == null) {
            String tchome = getTomcatHome();

            String defAjp12File = tchome + "/conf/ajp12.id";
            String defAjp13File = tchome + "/conf/ajp13.id";
            String ajpFile = secretFile;

            int portSave = port;
            String hostSave = host;
            String secretSave = secret;

            boolean pickAjp = (!isAjp13 && !isAjp12);

            // try to read unset parameters from appropriate file
            try {
                if (ajpFile == null) {
                    if (isAjp13) {
                        ajpFile = defAjp13File;
                    } else if (isAjp12) {
                        ajpFile = defAjp12File;
                    } else {
                        File f = new File(defAjp13File);
                        if (f.exists()) {
                            ajpFile = defAjp13File;
                            isAjp13 = true;
                        } else {
                            f = new File(defAjp12File);
                            if (f.exists()) {
                                ajpFile = defAjp12File;
                                isAjp12 = true;
                            }
                        }
                    }
                } else if (pickAjp) {
                    // default to ajp12 if file specifed with no protocal
                    isAjp12 = true;
                }

                if (isAjp13) {
                    Properties props = new Properties();
                    props.load(new FileInputStream(ajpFile));

                    String line = props.getProperty("port");
                    if (port < 0) {
                        try {
                            port = Integer.parseInt(line);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                    }

                    line = props.getProperty("address");
                    if (host == null) host = line;
                    line = props.getProperty("secret");
                    if (secret == null) secret = line;
                    shutdown = props.getProperty("shutdown");
                    // if shutdown not enabled in default ajp13 file and
                    // picking ajp, try ajp12 default file
                    if (shutdown == null && secretFile == null && pickAjp) {
                        isAjp12 = true;
                        isAjp13 = false;
                        port = portSave;
                        host = hostSave;
                        secret = secretSave;
                        ajpFile = defAjp12File;
                    }
                }

                if (isAjp12) {
                    BufferedReader rd = new BufferedReader
                            (new FileReader(ajpFile));
                    String line = rd.readLine();

                    if (port < 0) {
                        try {
                            port = Integer.parseInt(line);
                        } catch (NumberFormatException ex) {
                            ex.printStackTrace();
                        }
                    }

                    line = rd.readLine();
                    if (host == null) host = line;
                    line = rd.readLine();
                    if (secret == null) secret = line;
                }
            } catch (IOException ex) {
                //ex.printStackTrace();
                System.out.println("Can't read " + ajpFile);
                // System.out.println(ex.toString());
                if (!commandLineParams)
                    return;
            }
        }

        if ("".equals(secret))
            secret = null;

        System.out.println("Stopping tomcat on " + host + ":" + port + " "
                + secret);
        InetAddress address = null;
        if (host != null && !"".equals(host)) {
            try {
                address = InetAddress.getByName(host);
            } catch (UnknownHostException ex) {
                ex.printStackTrace();
            }
        }
        stopTomcat(address, port, secret);
    }

    public String getTomcatHome() {
        if (tomcatHome != null)
            return tomcatHome;
        // Try to establish install and home locations
        String tchome = IntrospectionUtils.guessInstall("tomcat.install",
                "tomcat.home", "stop-tomcat.jar");
        // Use the "tomcat.home" property to resolve the default filename
        tchome = System.getProperty("tomcat.home");
        if (tchome == null) {
            System.out.println(sm.getString("tomcat.nohome"));
            tchome = ".";
            // Assume current working directory
        }
        return tchome;
    }

    /**
     * This particular implementation will search for an AJP12
     * connector ( that have a special stop command ).
     */
    public void stopTomcat(InetAddress address, int portInt, String secret)
            throws IOException {
        // use Ajp12 to stop the server...
        try {
            if (address == null)
                address = InetAddress.getLocalHost();
            Socket socket = new Socket(address, portInt);
            OutputStream os = socket.getOutputStream();
            if (isAjp13)
                sendAjp13Stop(os, secret);
            else
                sendAjp12Stop(os, secret);

            // Setting soLinger to 0 will help make sure the connection is
            // closed on NetWare.  If the other side closes the connection
            // first, we get a SocketException so catch and ignore it
            try {
                socket.setSoLinger(true, 0);
            } catch (java.net.SocketException ignore) {
            }
            os.flush();
            os.close();
            //	    socket.close();
        } catch (IOException ex) {
            System.out.println("Error stopping Tomcat with Ajp12 on " +
                    address + ":" + portInt + " " + ex);
        }
    }

    /**
     * Small AJP12 client util
     */
    public void sendAjp12Stop(OutputStream os, String secret)
            throws IOException {
        byte stopMessage[] = new byte[2];
        stopMessage[0] = (byte) 254;
        stopMessage[1] = (byte) 15;
        os.write(stopMessage);
        if (secret != null)
            sendAjp12String(os, secret);

        // flush the stream and give the backend a chance to read the request
        // and shut down before we close the socket
        os.flush();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignore) {
        }
    }

    /**
     * Small AJP13 client util
     */
    public void sendAjp13Stop(OutputStream os, String secret)
            throws IOException {
        byte stopMessage[] = new byte[5];
        stopMessage[0] = (byte) 0x12;
        stopMessage[1] = (byte) 0x34;
        int len = 1;
        if (secret != null)
            len = secret.length() + 4; // 1==shutdown cmd, 2==string len, 1=\0
        stopMessage[2] = (byte) (len / 256);
        stopMessage[3] = (byte) (len % 256);
        stopMessage[4] = 7; // JK_AJP13_SHUTDOWN

        os.write(stopMessage);
        if (secret != null)
            sendAjp13String(os, secret);

        // flush the stream and give the backend a chance to read the request
        // and shut down before we close the socket
        os.flush();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignore) {
        }
    }

    /**
     * Small AJP12 client util
     */
    public void sendAjp12String(OutputStream os, String s)
            throws IOException {
        int len = s.length();
        os.write(len / 256);
        os.write(len % 256);
        os.write(s.getBytes());// works only for ascii
    }

    /**
     * Small AJP13 client util
     */
    public void sendAjp13String(OutputStream os, String s)
            throws IOException {
        int len = s.length();
        os.write(len / 256);
        os.write(len % 256);
        os.write(s.getBytes());// works only for ascii
        os.write((byte) 0);
    }

    /**
     * Process arguments - set object properties from the list of args.
     */
    public boolean processArgs(String[] args) {
        try {
            return IntrospectionUtils.processArgs(this, args, getOptions1(),
                    null, getOptionAliases());
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }

    }

    static String options1[] = {"help", "stop", "ajp12", "ajp13"};
    static Hashtable optionAliases = new Hashtable();
    static Hashtable optionDescription = new Hashtable();

    static {
        optionAliases.put("h", "home");
        optionAliases.put("?", "help");
    }

    public String[] getOptions1() {
        return options1;
    }

    public Hashtable getOptionAliases() {
        return optionAliases;
    }

    public static void printUsage() {
        System.out.println("Usage: java org.apache.tomcat.startup.StopTomcat {options}");
        System.out.println("  Options are:");
        System.out.println("    -ajp12                            Use Ajp12 protocol for shutdown");
        System.out.println("    -ajp13                            Use Ajp13 protocol for shutdown");
        System.out.println("                                          Shutdown feature of Ajp13");
        System.out.println("                                          connector must be enabled.");
        System.out.println("    -ajpid file (or -secretFile file) Use this file instead of conf/ajp13.id");
        System.out.println("                                          or conf/ajp12.id.  You must set");
        System.out.println("                                          -ajp13 option if file is from");
        System.out.println("                                          Ajp13 connector");
        System.out.println("    -help (or -?)                     Show this usage report");
        System.out.println("    -host <host>                      Host to send the shutdown command");
        System.out.println("    -home <dir> (or -h <dir>)         Use this directory as tomcat.home, to");
        System.out.println("                                          find conf/ajp13.id or conf/ajp12.id");
        System.out.println("    -pass <pw> (or -secret <pw>)      Password to use, aka secret");
        System.out.println("    -port <port>                      Port to send the shutdown command");
        System.out.println("Note: the '-' on the options is optional.");
        System.out.println();
    }

    public static void main(String args[]) {
        try {
            StopTomcat tomcat = new StopTomcat();
            tomcat.setArgs(args);
            tomcat.execute();
        } catch (Exception ex) {
            System.out.println(sm.getString("tomcat.fatal"));
            ex.printStackTrace();
            System.exit(1);
        }
    }


}
